/*
 * Sapphire
 *
 * Copyright (C) 2018 eq
 * Copyright (C) 2018 Alyssa Rosenzweig
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 *
 */

const sanitizationConfig = {
    allowedElements: {
        b: {},
        strong: {},
        i: {},
        em: {},
        u: {},
        s: {},
        br: {},
        a: {href: 'href'},
    },
    allowedProtocols: ['http', 'https', 'mailto', 'gopher']
};

const isProtocolSafe = function(str) {
    const s = str.split(':');
    return s.length > 1 && sanitizationConfig.allowedProtocols.includes(s[0]);
};

const stripProtocol = function(href) {
    return href.split(':').slice(1).join(':');
};

const addSanitizedChildren = function(newElement, from) {
    for (const node of from.childNodes) {
        newElement.appendChild(sanitizeNode(node));
    }
};

// TODO: some sort of time limit so bad actors can't DDOS the client with huge
// element trees
const sanitizeElement = function(element) {
    const tagName = element.tagName.toLowerCase();
    if (sanitizationConfig.allowedElements[tagName]) {
        let newElement = document.createElement(tagName);

        /* A link to append to the end */
        let postfixLink = null;

        for (const attr of element.getAttributeNames()) {
            const attrType = sanitizationConfig.allowedElements[tagName][attr];
            const attrVal = element.getAttribute(attr);

            // TODO: other element types
            if (attrType === 'href' && isProtocolSafe(attrVal)) {
                /* We have a link to a safe protocol. Check if the link text
                 * matches the destination (safe links), or at least the
                 * destination with the protocol stripped, common for mailto.
                 */

                const text = element.innerText;

                if (text == attrVal || text == stripProtocol(attrVal)) {
                    newElement.setAttribute(attr, attrVal);
                } else {
                    /* This is a deceptive link destination, since the text
                     * doesn't match.  Add a dedicated link to the side. There
                     * must be no existing properties for this to function */

                    postfixLink = attrVal;
                    newElement = document.createElement("span");
                }
            }
        }

        addSanitizedChildren(newElement, element);

        if (postfixLink) {
            /* Append a link destination */
            const linkEl = document.createElement('a');
            linkEl.setAttribute('href', postfixLink);
            linkEl.innerText = postfixLink;

            newElement.appendChild(document.createTextNode(' ('));
            newElement.appendChild(linkEl);
            newElement.appendChild(document.createTextNode(')'));
        }

        return newElement;
    } else {
        /* The element was not allowed. Strip it, but still allow through its
         * (sanitized) children */

        const newElement = document.createElement("span");
        addSanitizedChildren(newElement, element);

        return newElement;
    }
};

const sanitizeNode = function(node) {
    if (node.nodeType === Node.ELEMENT_NODE) {
        return sanitizeElement(node);
    } else if (node.nodeType === Node.TEXT_NODE) {
        return document.createTextNode(node.textContent);
    } else {
        return document.createTextNode('');
    }
};

window.sanitizeHtmlString = function(str) {
    const element = new DOMParser().parseFromString(str, 'text/html').body;
    const newElement = document.createElement('div');

    element.innerHTML = str;

    addSanitizedChildren(newElement, element);

    return newElement.innerHTML;
};
